#![allow(non_snake_case)]
use crate::{
    json_val_to_string, Input, InputPrefix, InputRef, InputSuffix, Popover, PopoverTrigger,
    ServerError, ValidType,
};
use either_of::Either;
use icondata as i;
use leptos::{
    ev,
    html,
    prelude::*,
};
use leptos_icons::*;
use serde::{Deserialize, Serialize};
use shq_common::prelude::{options::FetchParams, Size};
use thaw_components::{Follower, FollowerPlacement, FollowerWidth};
use thaw_utils::{ArcOneCallback, ComponentRef};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AutoCompleteOption {
    pub label: String,
    pub value: String,
}
#[component]
pub fn AutoSearch(
    fetch_params: StoredValue<FetchParams>,
    #[prop(optional, into)] label: MaybeProp<String>,
    value: Option<String>,
    #[prop(optional, into)] on_change: Option<ArcOneCallback<Option<String>>>,
    #[prop(optional, into)] on_change_with_label: Option<ArcOneCallback<Option<(String,String)>>>,
    #[prop(optional, into)] placeholder: MaybeProp<String>,
    #[prop(optional, into)] blur_after_select: MaybeProp<bool>,
    #[prop(optional, into)] disabled: MaybeProp<bool>,
    #[prop(optional, into)] allow_free_input: bool,
    #[prop(optional, into)] valid_type: MaybeProp<ValidType>,
    #[prop(optional, into)] size: MaybeProp<Size>,
    #[prop(optional)] comp_ref: ComponentRef<AutoCompleteRef>,
    #[prop(default=true.into(), into)] border: MaybeProp<bool>,
    #[prop(into)] err_msg: MaybeProp<String>,
    #[prop(optional)] inner_max_width: u32,
    #[prop(optional)] is_init_focus: bool,
) -> impl IntoView {
    let search_value: RwSignal<Option<String>> = RwSignal::new(None);
    let curr_label: RwSignal<Option<String>> = RwSignal::new(value);
    let curr_value: RwSignal<Option<String>> = RwSignal::new(None);
    let input_ref = ComponentRef::<InputRef>::new();
    let default_index = if allow_free_input { None } else { Some(0) };
    let select_option_index = RwSignal::<Option<usize>>::new(default_index);
    let select_options: RwSignal<Vec<AutoCompleteOption>> = RwSignal::new(Vec::new());
    let menu_ref = NodeRef::<html::Div>::new();
    let is_show_menu = RwSignal::new(false);
    let auto_complete_ref = NodeRef::<html::Div>::new();
    let open_menu = move || {
        select_option_index.set(default_index);
        is_show_menu.set(true);
    };
    let allow_value = move |_| {
        if !is_show_menu.get() {
            open_menu();
        }
        true
    };
    let options_source = LocalResource::new(move || {
        let val = search_value.get();
        let fp = fetch_params.get_value();
        let filters = if let Some(val) = val {
            fp.search_cols.as_ref().map(|scols| {
                let mut wc = "".to_string();
                for (i, col) in scols.iter().enumerate() {
                    wc.push_str(&format!("`{}` like '%{}%' ", col, val));
                    if i != scols.len() - 1 {
                        wc.push_str("or ");
                    }
                }
                wc
            })
        } else {
            None
        };
        search_options(fp, filters)
    });
    let select_value = Action::new(move |val: &String| {
        let value = val.clone();
        let on_select = on_change.clone();
        let on_select_with_label = on_change_with_label.clone();
        async move {
            curr_value.set(Some(value.clone()));
            if let Some(on_select) = on_select {
                on_select(Some(value.clone()));
            }
            if let Some(on_select_with_label) = on_select_with_label {
                let label=curr_label.get().unwrap_or_default();
                on_select_with_label(Some((value, label)));
            }
            if allow_free_input {
                select_option_index.set(None);
            }
            is_show_menu.set(false);
            if blur_after_select.get().unwrap_or(false) {
                if let Some(input_ref) = input_ref.get_untracked() {
                    input_ref.blur();
                }
            }
        }
    });
    let label_view = move || {
        if let Some(label) = label.get() {
            Some(view! { <>{label}</> })
        } else {
            None
        }
    };
    let err_msg_view = move || {
        if let Some(msg) = err_msg.get() {
            view! { <Popover>
                        <PopoverTrigger slot>
                            <div><Icon icon=i::BiErrorCircleRegular style="color: var(--color-red-500)"/></div>
                        </PopoverTrigger>
                        {msg}
                    </Popover>}.into()
        } else {
            None
        }
    };
    let on_keydown = move |key: &str| {
        let max_idx = select_options.with(|opts| opts.len()-1);
        if key == "ArrowDown" {
            select_option_index.update(|index| {
                if index.map(|i| i < max_idx).unwrap_or(false) {
                    let i = index.unwrap_or(0);
                    *index = Some(i + 1);
                }
            });
        } else if key == "ArrowUp" {
            select_option_index.update(|index| {
                match *index {
                    None => *index = Some(0),
                    Some(0) => {
                        if allow_free_input {
                            *index = None
                        }
                    }
                    Some(prev_index) => *index = Some(prev_index - 1),
                };
            });
        } else if key == "Enter" {
            let index = select_option_index.get();
            let opt = match index {
                None if allow_free_input => None,
                Some(index) => {
                    let option = select_options.with(|opts| opts[index].clone());
                    Some(option)
                }
                _ => None,
            };
            if let Some(opt) = opt {
                curr_label.set(Some(opt.label.clone()));
                select_value.dispatch(opt.value);
            } else {
                curr_label.set(None);
                curr_value.set(None);
            }
        }
    };
    comp_ref.load(AutoCompleteRef { input_ref });
    let on_change = move |val: Option<String>| {
        search_value.set(val);
    };
    view! {
        <crate::_binder::Binder>
            <div
                node_ref=auto_complete_ref
                on:keydown=move |ev: ev::KeyboardEvent| {
                    let key = ev.key();
                    if key == "ArrowDown" || key == "ArrowUp" || key == "Enter" {
                        ev.prevent_default();
                        ev.stop_propagation();
                        on_keydown(&ev.key());
                    }
                }
            >
                <Input
                    value=curr_label
                    placeholder
                    disabled
                    valid_type
                    size
                    on_focus=move |_| open_menu()
                    on_blur=move |_| is_show_menu.set(false)
                    on_change
                    allow_value
                    comp_ref=input_ref
                    inner_max_width
                    border
                    is_init_focus
                >
                    <InputPrefix slot>
                        {label_view}
                    </InputPrefix>
                    <InputSuffix slot>
                        {err_msg_view()}
                    </InputSuffix>
                </Input>
            </div>
            <Follower
                slot
                show=is_show_menu
                placement=FollowerPlacement::BottomStart
                width=FollowerWidth::Target
                auto_height=true
            >
                <div
                    class="shadow bg-base-200 rounded-box z-10"
                    class=("hidden",move||!is_show_menu.get())
                >
                    <Transition fallback=|| view! { <progress class="progress w-56"></progress> }>
                        <Show
                            when= move || options_source.get().as_deref().is_some()
                            fallback=|| view! { <p>"Loading..."</p> }
                        >
                            {
                                match options_source.get().as_deref().cloned().unwrap() {
                                    Ok(options) => {
                                        let blank=options.is_empty();
                                        select_options.set(options.clone());
                                        let opts_view=if blank {
                                            None
                                        }else{
                                            let opts_view=options
                                            .into_iter()
                                            .enumerate()
                                            .map(|(index, v)| {
                                                let menu_item_ref = NodeRef::<html::Li>::new();
                                                let label= v.label.clone();
                                                let on_click = move |_| {
                                                    curr_label.set(Some(label.clone()));
                                                    curr_value.set(Some(v.value.clone()));
                                                    select_value.dispatch(v.value.clone());
                                                };
                                                let on_mouseenter = move |_| {
                                                    select_option_index.set(Some(index));
                                                };
                                                let on_mousedown = move |ev: ev::MouseEvent| {
                                                    ev.prevent_default();
                                                };
                                                Effect::new(move |_| {
                                                    if Some(index) == select_option_index.get() {
                                                        if !is_show_menu.get() {
                                                            return;
                                                        }
                                                        if let Some(menu_item_ref) = menu_item_ref.get() {
                                                            if let Some(menu_ref) = menu_ref.get(){
                                                                let menu_rect = menu_ref.get_bounding_client_rect();
                                                                let item_rect = menu_item_ref.get_bounding_client_rect();
                                                                if item_rect.y() < menu_rect.y() {
                                                                    menu_item_ref.scroll_into_view_with_bool(true);
                                                                } else if item_rect.y() + item_rect.height()
                                                                    > menu_rect.y() + menu_rect.height()
                                                                {
                                                                    menu_item_ref.scroll_into_view_with_bool(false);
                                                                }
                                                            }
                                                        }
                                                    }
                                                });
                                                view! {
                                                    <li
                                                        class="menu-item"
                                                        on:click=on_click
                                                        on:mousedown=on_mousedown
                                                        on:mouseenter=on_mouseenter
                                                        node_ref=menu_item_ref
                                                    >
                                                        <a class=("active",move || Some(index) == select_option_index.get())
                                                        >
                                                            {v.label.clone()}
                                                        </a>
                                                    </li>
                                                }
                                            })
                                            .collect_view();
                                            Some(view! {
                                                <div class="flex-1 bg-base-100 overflow-auto ">
                                                    <ul class="menu">
                                                        {opts_view}
                                                    </ul>
                                                </div>
                                            })

                                        };
                                        Either::Left(opts_view)
                                    }
                                    Err(err) => Either::Right(view! {
                                        <div class="flex-1 bg-base-100 overflow-auto ">
                                            <ServerError err_msg=err.to_string().into()/>
                                        </div>
                                    }),
                                }
                            }
                        </Show>
                    </Transition>
                </div>
            </Follower>
        </crate::_binder::Binder>
    }
}

#[derive(Clone)]
pub struct AutoCompleteRef {
    input_ref: ComponentRef<InputRef>,
}

impl AutoCompleteRef {
    pub fn focus(&self) {
        if let Some(input_ref) = self.input_ref.get_untracked() {
            input_ref.focus();
        }
    }
    pub fn blur(&self) {
        if let Some(input_ref) = self.input_ref.get_untracked() {
            input_ref.blur();
        }
    }
}
#[server]
pub async fn get_input_value(
    fetch_params: FetchParams,
    val: Option<String>,
) -> Result<Option<AutoCompleteOption>, ServerFnError> {
    if val.as_ref().map(|v| v.is_empty()).unwrap_or(false) {
        return Ok(None);
    }
    let tname = fetch_params.table_name.clone();
    let filters = format!("`{}` = '{}'", fetch_params.value_col, val.as_ref().unwrap());
    search_options(fetch_params, Some(filters))
        .await
        .map(|opts| {
            if opts.is_empty() {
                None
            } else {
                Some(AutoCompleteOption {
                    value: opts[0].value.clone(),
                    label: opts[0].label.clone(),
                })
            }
        })
        .map_err(|e| ServerFnError::new(format!("获取{}数据失败,{}", tname, e)))
}
#[server(SearchOptions, "/api")]
pub async fn search_options(
    fetch_params: FetchParams,
    filters: Option<String>,
) -> Result<Vec<AutoCompleteOption>, ServerFnError> {
    if filters.is_none() {
        return Ok(vec![]);
    }
    let table_name = fetch_params.table_name.to_owned();
    let mut sel_cols: Vec<String> = Vec::new();
    sel_cols.push(fetch_params.label_col.clone());
    sel_cols.push(fetch_params.value_col.clone());
    if let Some(pc) = fetch_params.parent_col.as_ref() {
        sel_cols.push(pc.clone());
    }
    let orders = fetch_params.order_by.clone();
    let os = orders.as_deref();
    let sel_cols = sel_cols.iter().map(|s| s.as_str()).collect::<Vec<&str>>();
    let res = shq_common::prelude::find_list_data(
        table_name.as_str(),
        sel_cols.as_slice(),
        filters.as_deref(),
        os,
    )
    .await;
    match res {
        Ok(list) => {
            let mut opts: Vec<AutoCompleteOption> = Vec::new();
            for item in list.into_iter() {
                if let serde_json::Value::Object(map) = item {
                    let value = map
                        .get(&fetch_params.value_col)
                        .map(|v| json_val_to_string(v))
                        .unwrap();
                    let label = map
                        .get(&fetch_params.label_col)
                        .map(|v| json_val_to_string(v))
                        .unwrap();
                    let opt = AutoCompleteOption { value, label };
                    opts.push(opt);
                }
            }
            Ok(opts)
        }
        Err(e) => {
            let err_msg = format!("获取{}数据失败,{}", &fetch_params.table_name, e);
            Err(ServerFnError::new(err_msg))
        }
    }
}
